/****************************************************************************
 * arch/arm/src/stm32h5/stm32_adc.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <sys/ioctl.h>

#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <string.h>

#include <arch/board/board.h>
#include <nuttx/nuttx.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/analog/adc.h>
#include <nuttx/analog/ioctl.h>
#include <nuttx/power/pm.h>

#include "chip.h"
#include "stm32_adc.h"
#include "stm32_tim.h"
#include "stm32_rcc.h"
#include "stm32_dma.h"

/* ADC "upper half" support must be enabled */

#ifdef CONFIG_ADC

#if defined(CONFIG_STM32H5_ADC1) || defined(CONFIG_STM32H5_ADC2)

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#ifdef CONFIG_PM
#pragma message "Power Management not implemented in H5 ADC driver. "
#endif

#ifndef ANIOC_SET_OVERSAMPLE
#  define ANIOC_SET_OVERSAMPLE _ANIOC(0x0f)
#endif

/* ADC Channels/DMA *********************************************************/

#define ADC_SMPR_DEFAULT    ADC_SMPR_640p5
#define ADC_SMPR1_DEFAULT   ((ADC_SMPR_DEFAULT << ADC_SMPR1_SMP0_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP1_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP2_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP3_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP4_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP5_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP6_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP7_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP8_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR1_SMP9_SHIFT))
#define ADC_SMPR2_DEFAULT   ((ADC_SMPR_DEFAULT << ADC_SMPR2_SMP10_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP11_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP12_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP13_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP14_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP15_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP16_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP17_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP18_SHIFT) | \
                             (ADC_SMPR_DEFAULT << ADC_SMPR2_SMP19_SHIFT))

#define ADC_DIFSEL_DEFAULT    0
#define ADC_DIFSEL_ALL_SINGLE 0x0
#define ADC_DIFSEL_ALL_DIFF   0xFFFFF

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* This structure describes the state of one ADC block */

struct stm32_dev_s
{
  const struct adc_callback_s *cb;
  uint8_t irq;          /* Interrupt generated by this ADC block */
  uint8_t rnchannels;   /* Number of channels */
  uint8_t cchannels;    /* Number of configured channels */
  uint8_t intf;         /* ADC interface number */
  uint8_t current;      /* Current ADC channel being converted */
  uint8_t resolution;   /* ADC resolution (0-3) */
  bool    hasdma;       /* True: This ADC supports DMA */
#ifdef ADC_HAVE_DMA
  uint16_t dmabatch;    /* Number of conversions for DMA batch */
  bool     circular;    /* 0 = one-shot, 1 = circular */
#endif
#ifdef ADC_HAVE_TIMER
  uint8_t trigger;      /* Timer trigger channel: 0=CC1, 1=CC2, 2=CC3,
                         * 3=CC4, 4=TRGO, 5=TRGO2
                         */
#endif
  xcpt_t   isr;         /* Interrupt handler for this ADC block */
  uint32_t base;        /* Base address of registers unique to this ADC
                         * block */
  uint32_t mbase;       /* Base address of master ADC (allows for access to
                         * shared common registers) */
  bool     initialized; /* Keeps track of the initialization status of the ADC */
#ifdef ADC_HAVE_TIMER
  uint32_t tbase;       /* Base address of timer used by this ADC block */
  uint32_t trcc_enr;    /* RCC ENR Register */
  uint32_t trcc_en;     /* RCC EN Bit in ENR Register */
  uint32_t extsel;      /* EXTSEL value used by this ADC block */
  uint32_t pclck;       /* The PCLK frequency that drives this timer */
  uint32_t freq;        /* The desired frequency of conversions */
#endif

  uint32_t difsel;      /* ADCx_DIFSEL (Differential Mode) value */
  uint32_t smpr1;       /* ADCx_SMPR1 (Sample time 1) value */
  uint32_t smpr2;       /* ADCx_SMPR2 (Sample time 2) value */

#ifdef CONFIG_PM
  struct pm_callback_s pm_callback;
#endif

#ifdef ADC_HAVE_DMA
  DMA_HANDLE dma;        /* Allocated DMA channel */
  uint16_t *r_dmabuffer; /* DMA transfer buffer */
#endif

  bool wdg1_enable;          /* True - Analog Watchdog 1 Enabled */
#ifdef ADC_HAVE_WDG1
  uint16_t wdg1_low_thresh;  /* AWD1 Thresholds. Initialized by Kconfig */
  uint16_t wdg1_high_thresh; /* and can be changed with ioctl */
  uint8_t wdg1_flt;          /* wdg1_flt+1 events triggers the watchdog */
  bool wdg1_single_chan;     /* True - Single Channel monitored.
                              * False - All Channels monitored */
  uint8_t wdg1_chan;         /* AWD1 Channel for single mode */
#endif

  bool oversample; /* True - Oversampling enabled */
#ifdef ADC_HAVE_OVERSAMPLE
  bool trovs;      /* True - Each oversampled conversion needs a trigger */
  uint8_t ovsr;    /* Oversampling Ratio = 2^(ovsr+1) */
  uint8_t ovss;    /* Oversampling shift bits (max 16-bit result) */
#endif

  /* List of selected ADC channels to sample */

  uint8_t chanlist[CONFIG_STM32H5_ADC_MAX_SAMPLES];
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* ADC Register access */

static uint32_t adc_getreg(struct stm32_dev_s *priv, int offset);
static void adc_putreg(struct stm32_dev_s *priv, int offset, uint32_t value);
static void adc_modifyreg(struct stm32_dev_s *priv, int offset,
                              uint32_t clrbits, uint32_t setbits);

#ifdef ADC_HAVE_TIMER
static uint16_t tim_getreg(struct stm32_dev_s *priv, int offset);
static void     tim_putreg(struct stm32_dev_s *priv, int offset,
                           uint16_t value);
static void     tim_modifyreg(struct stm32_dev_s *priv, int offset,
                              uint16_t clrbits, uint16_t setbits);
static void     tim_modifyreg32(struct stm32_dev_s *priv, int offset,
                                uint32_t clrbits, uint32_t setbits);
static void     tim_dumpregs(struct stm32_dev_s *priv,
                             const char *msg);
#endif

/* ADC Miscellaneous Helpers */

static void adc_rccreset(struct stm32_dev_s *priv, bool reset);
static void adc_setupclock(struct stm32_dev_s *priv);
static void adc_enable(struct stm32_dev_s *priv);
static uint32_t adc_sqrbits(struct stm32_dev_s *priv, int first,
                            int last, int offset);
static int  adc_set_ch(struct adc_dev_s *dev, uint8_t ch);
static bool adc_internal(struct stm32_dev_s * priv, uint32_t *adc_ccr);
static void adc_startconv(struct stm32_dev_s *priv, bool enable);
#ifdef ADC_HAVE_WDG1
static void adc_wdog1_enable(struct stm32_dev_s *priv);
static void adc_wdog1_init(struct stm32_dev_s *priv);
#endif

#ifdef ADC_HAVE_TIMER
static void adc_timstart(struct stm32_dev_s *priv, bool enable);
static int  adc_timinit(struct stm32_dev_s *priv);
#endif

#ifdef ADC_HAVE_DMA
static void adc_dmaconvcallback(DMA_HANDLE handle, uint8_t status,
                                void *arg);
static void adc_dmacfg(struct stm32_dev_s *priv,
                               struct stm32_gpdma_cfg_s *cfg);
static void adc_reset_dma(struct adc_dev_s *dev);
#endif

#ifdef ADC_HAVE_OVERSAMPLE
static void adc_oversample(struct adc_dev_s *dev);
#endif

/* ADC Interrupt Handler */

static int adc_interrupt(struct adc_dev_s *dev, uint32_t regval);
static int adc12_interrupt(int irq, void *context, void *arg);

/* ADC Driver Methods */

static int  adc_bind(struct adc_dev_s *dev,
                     const struct adc_callback_s *callback);
static void adc_reset(struct adc_dev_s *dev);
static int  adc_setup(struct adc_dev_s *dev);
static void adc_shutdown(struct adc_dev_s *dev);
static void adc_rxint(struct adc_dev_s *dev, bool enable);
static int  adc_ioctl(struct adc_dev_s *dev, int cmd, unsigned long arg);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* ADC interface operations */

static const struct adc_ops_s g_adcops =
{
  .ao_bind      = adc_bind,
  .ao_reset     = adc_reset,
  .ao_setup     = adc_setup,
  .ao_shutdown  = adc_shutdown,
  .ao_rxint     = adc_rxint,
  .ao_ioctl     = adc_ioctl,
};

/* ADC1 state */

#ifdef CONFIG_STM32H5_ADC1

/* Double the size of the buffer in circular mode
 * Circular mode utilizes half-transfer DMA interrupts and a 2x buffer
 * to implement "double buffer" operation. While the CPU is servicing
 * one half, DMA is filling the other half.
 */

#ifdef ADC1_HAVE_DMA
#  ifdef CONFIG_STM32H5_ADC1_DMA_CFG
#    define ADC1_DMA_BUFFER_SIZE (CONFIG_STM32H5_ADC_MAX_SAMPLES *\
                                  CONFIG_STM32H5_ADC1_DMA_BATCH * 2)
#  else
#    define ADC1_DMA_BUFFER_SIZE (CONFIG_STM32H5_ADC_MAX_SAMPLES *\
                                  CONFIG_STM32H5_ADC1_DMA_BATCH)
#  endif

static uint16_t g_adc1_dmabuffer[ADC1_DMA_BUFFER_SIZE]
__attribute__((aligned(32)));

#endif

static struct stm32_dev_s g_adcpriv1 =
{
  .irq         = STM32_IRQ_ADC1,
  .isr         = adc12_interrupt,
  .intf        = 1,
  .resolution  = CONFIG_STM32H5_ADC1_RESOLUTION,
  .base        = STM32_ADC1_BASE,
  .mbase       = STM32_ADC1_BASE,
  .initialized = false,
#ifdef ADC1_HAVE_TIMER
  .trigger     = CONFIG_STM32H5_ADC1_TIMTRIG,
  .tbase       = ADC1_TIMER_BASE,
  .trcc_enr    = ADC1_TIMER_RCC_ENR,
  .trcc_en     = ADC1_TIMER_RCC_EN,
  .extsel      = ADC1_EXTSEL_VALUE,
  .pclck       = ADC1_TIMER_PCLK_FREQUENCY,
  .freq        = CONFIG_STM32H5_ADC1_SAMPLE_FREQUENCY,
#endif

#ifdef BOARD_ADC1_DIFSEL
  .difsel      = BOARD_ADC1_DIFSEL,
#else
  .difsel      = ADC_DIFSEL_DEFAULT,
#endif

#ifdef BOARD_ADC1_SMPR1
  .smpr1       = BOARD_ADC1_SMPR1,
#else
  .smpr1       = ADC_SMPR1_DEFAULT,
#endif

#ifdef BOARD_ADC1_SMPR2
  .smpr2       = BOARD_ADC1_SMPR2,
#else
  .smpr2       = ADC_SMPR2_DEFAULT,
#endif

#ifdef ADC1_HAVE_DMA
  .hasdma      = true,
  .r_dmabuffer = g_adc1_dmabuffer,
  .dmabatch    = CONFIG_STM32H5_ADC1_DMA_BATCH,
#  ifdef CONFIG_STM32H5_ADC1_DMA_CFG
  .circular    = true,
#  else
  .circular    = false,
#  endif
#else
  .hasdma      = false,
#endif

#ifdef ADC1_HAVE_OVERSAMPLE
  .oversample = true,
#  ifdef CONFIG_STM32H5_ADC1_TROVS
  .trovs = true,
#  else
  .trovs = false,
#  endif
  .ovsr = CONFIG_STM32H5_ADC1_OVSR,
  .ovss = CONFIG_STM32H5_ADC1_OVSS,
#else
  .oversample = false,
#endif

#ifdef ADC1_HAVE_WDG1
  .wdg1_enable = true,
  .wdg1_flt = CONFIG_STM32H5_ADC1_WDG1_FLT,
  .wdg1_low_thresh = CONFIG_STM32H5_ADC1_WDG1_LOWTHRESH,
  .wdg1_high_thresh = CONFIG_STM32H5_ADC1_WDG1_HIGHTHRESH,
#  ifdef CONFIG_STM32H5_ADC1_WDG1_SGL
  .wdg1_single_chan = true,
  .wdg1_chan = CONFIG_STM32H5_ADC1_WDG1_CHAN,
#  else
  .wdg1_single_chan = false,
  .wdg1_chan = 0,
#  endif
#else
  .wdg1_enable = false,
#endif
};

static struct adc_dev_s g_adcdev1 =
{
  .ad_ops       = &g_adcops,
  .ad_priv      = &g_adcpriv1,
};
#endif

/* ADC2 state */

#ifdef CONFIG_STM32H5_ADC2

#ifdef ADC2_HAVE_DMA
#  ifdef CONFIG_STM32H5_ADC2_DMA_CFG
#    define ADC2_DMA_BUFFER_SIZE (CONFIG_STM32H5_ADC_MAX_SAMPLES *\
                                  CONFIG_STM32H5_ADC2_DMA_BATCH * 2)
#  else
#    define ADC2_DMA_BUFFER_SIZE (CONFIG_STM32H5_ADC_MAX_SAMPLES *\
                                  CONFIG_STM32H5_ADC2_DMA_BATCH)
#  endif

static uint16_t g_adc2_dmabuffer[ADC2_DMA_BUFFER_SIZE]
__attribute__((aligned(32)));
#endif

static struct stm32_dev_s g_adcpriv2 =
{
  .irq         = STM32_IRQ_ADC2,
  .isr         = adc12_interrupt,
  .intf        = 2,
  .resolution  = CONFIG_STM32H5_ADC2_RESOLUTION,
  .base        = STM32_ADC2_BASE,
  .mbase       = STM32_ADC2_BASE,
  .initialized = false,
#ifdef ADC2_HAVE_TIMER
  .trigger     = CONFIG_STM32H5_ADC2_TIMTRIG,
  .tbase       = ADC2_TIMER_BASE,
  .trcc_enr    = ADC2_TIMER_RCC_ENR,
  .trcc_en     = ADC2_TIMER_RCC_EN,
  .extsel      = ADC2_EXTSEL_VALUE,
  .pclck       = ADC2_TIMER_PCLK_FREQUENCY,
  .freq        = CONFIG_STM32H5_ADC2_SAMPLE_FREQUENCY,
#endif

#ifdef BOARD_ADC2_DIFSEL
  .difsel      = BOARD_ADC2_DIFSEL,
#else
  .difsel      = ADC_DIFSEL_DEFAULT,
#endif

#ifdef BOARD_ADC2_SMPR1
  .smpr1       = BOARD_ADC2_SMPR1,
#else
  .smpr1       = ADC_SMPR1_DEFAULT,
#endif

#ifdef BOARD_ADC2_SMPR2
  .smpr2       = BOARD_ADC2_SMPR2,
#else
  .smpr2       = ADC_SMPR2_DEFAULT,
#endif

#ifdef ADC2_HAVE_DMA
  .hasdma      = true,
  .r_dmabuffer = g_adc2_dmabuffer,
  .dmabatch    = CONFIG_STM32H5_ADC2_DMA_BATCH,
#  ifdef CONFIG_STM32H5_ADC2_DMA_CFG
  .circular    = true,
#  else
  .circular    = false,
#  endif
#else
  .hasdma      = false,
#endif

#ifdef ADC2_HAVE_OVERSAMPLE
  .oversample = true,
#  ifdef CONFIG_STM32H5_ADC2_TROVS
  .trovs = true,
#  else
  .trovs = false,
#  endif
  .ovsr = CONFIG_STM32H5_ADC2_OVSR,
  .ovss = CONFIG_STM32H5_ADC2_OVSS,
#else
  .oversample = false,
#endif

#ifdef ADC2_HAVE_WDG1
  .wdg1_enable = true,
  .wdg1_flt = CONFIG_STM32H5_ADC2_WDG1_FLT,
  .wdg1_low_thresh = CONFIG_STM32H5_ADC2_WDG1_LOWTHRESH,
  .wdg1_high_thresh = CONFIG_STM32H5_ADC2_WDG1_HIGHTHRESH,
#  ifdef CONFIG_STM32H5_ADC2_WDG1_SGL
  .wdg1_single_chan = true,
  .wdg1_chan = CONFIG_STM32H5_ADC2_WDG1_CHAN,
#  else
  .wdg1_single_chan = false,
  .wdg1_chan = 0,
#  endif
#else
  .wdg1_enable = false,
#endif
};

static struct adc_dev_s g_adcdev2 =
{
  .ad_ops       = &g_adcops,
  .ad_priv      = &g_adcpriv2,
};
#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: adc_getreg
 *
 * Description:
 *   Read the value of an ADC register.
 *
 * Input Parameters:
 *   priv   - A reference to the ADC block status
 *   offset - The offset to the register to read
 *
 * Returned Value:
 *   The current contents of the specified register
 *
 ****************************************************************************/

static uint32_t adc_getreg(struct stm32_dev_s *priv, int offset)
{
  return getreg32(priv->base + offset);
}

/****************************************************************************
 * Name: adc_putreg
 *
 * Description:
 *   Write a value to an ADC register.
 *
 * Input Parameters:
 *   priv   - A reference to the ADC block status
 *   offset - The offset to the register to write to
 *   value  - The value to write to the register
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void adc_putreg(struct stm32_dev_s *priv, int offset,
                       uint32_t value)
{
  putreg32(value, priv->base + offset);
}

/****************************************************************************
 * Name: adc_modifyreg
 *
 * Description:
 *   Modify the value of an ADC register (not atomic).
 *
 * Input Parameters:
 *   priv    - A reference to the ADC block status
 *   offset  - The offset to the register to modify
 *   clrbits - The bits to clear
 *   setbits - The bits to set
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void adc_modifyreg(struct stm32_dev_s *priv, int offset,
                          uint32_t clrbits, uint32_t setbits)
{
  adc_putreg(priv, offset, (adc_getreg(priv, offset) & ~clrbits) | setbits);
}

/****************************************************************************
 * Name: adc_getregm
 *
 * Description:
 *   Read the value of an ADC register from the associated ADC master.
 *
 * Input Parameters:
 *   priv   - A reference to the ADC block status
 *   offset - The offset to the register to read
 *
 * Returned Value:
 *   The current contents of the specified register in the ADC master.
 *
 ****************************************************************************/

static uint32_t adc_getregm(struct stm32_dev_s *priv, int offset)
{
  return getreg32(priv->mbase + offset);
}

/****************************************************************************
 * Name: adc_putregm
 *
 * Description:
 *   Write a value to an ADC register in the associated ADC master.
 *
 * Input Parameters:
 *   priv   - A reference to the ADC block status
 *   offset - The offset to the register to write to
 *   value  - The value to write to the register
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void adc_putregm(struct stm32_dev_s *priv, int offset,
                        uint32_t value)
{
  putreg32(value, priv->mbase + offset);
}

/****************************************************************************
 * Name: adc_modifyregm
 *
 * Description:
 *   Modify the value of an ADC register in the associated ADC master
 *  (not atomic).
 *
 * Input Parameters:
 *   priv    - A reference to the ADC block status
 *   offset  - The offset to the register to modify
 *   clrbits - The bits to clear
 *   setbits - The bits to set
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void adc_modifyregm(struct stm32_dev_s *priv, int offset,
                           uint32_t clrbits, uint32_t setbits)
{
  adc_putregm(priv, offset,
              (adc_getregm(priv, offset) & ~clrbits) | setbits);
}

/****************************************************************************
 * Name: adc_enable
 *
 * Description:
 *   Enables the specified ADC peripheral.
 *
 * Input Parameters:
 *   priv - A reference to the ADC block status
 *
 * Returned Value:
 *
 ****************************************************************************/

static void adc_enable(struct stm32_dev_s *priv)
{
  uint32_t regval;

  regval = adc_getreg(priv, STM32_ADC_CR_OFFSET);

  /* Exit deep power down mode and enable voltage regulator */

  regval &= ~ADC_CR_DEEPPWD;
  adc_putreg(priv, STM32_ADC_CR_OFFSET, regval);

  regval = adc_getreg(priv, STM32_ADC_CR_OFFSET);
  regval |= ADC_CR_ADVREGEN;
  adc_putreg(priv, STM32_ADC_CR_OFFSET, regval);

  /* Wait for voltage regulator to power up */

  up_udelay(20);

  /* Perform single-ended and/or differential calibration if necessary */

  regval |= ADC_CR_ADCAL;

  /* Calibrate single-ended channels if necessary */

  if (priv->difsel != ADC_DIFSEL_ALL_DIFF)
    {
      regval &= ~ADC_CR_ADCALDIF;
      adc_putreg(priv, STM32_ADC_CR_OFFSET, regval);
      while (adc_getreg(priv, STM32_ADC_CR_OFFSET) & ADC_CR_ADCAL);
    }

  /* Calibrate differential channels if necessary */

  if (priv->difsel != ADC_DIFSEL_ALL_SINGLE)
    {
      regval |= ADC_CR_ADCALDIF;
      adc_putreg(priv, STM32_ADC_CR_OFFSET, regval);
      while (adc_getreg(priv, STM32_ADC_CR_OFFSET) & ADC_CR_ADCAL);
    }

  /* Enable ADC
   * Note: ADEN bit cannot be set during ADCAL=1 and 4 ADC clock cycle
   * after the ADCAL bit is cleared by hardware. If we are using SYSCLK
   * as ADC clock source, this is the same as time taken to execute 4
   * ARM instructions.
   */

  regval  = adc_getreg(priv, STM32_ADC_CR_OFFSET);
  regval |= ADC_CR_ADEN;
  adc_putreg(priv, STM32_ADC_CR_OFFSET, regval);

  /* Wait for hardware to be ready for conversions */

  while (!(adc_getreg(priv, STM32_ADC_ISR_OFFSET) & ADC_INT_ADRDY));

  adc_modifyreg(priv, STM32_ADC_ISR_OFFSET, 0, ADC_INT_ADRDY);
}

/****************************************************************************
 * Name: adc_bind
 *
 * Description:
 *   Bind the upper-half driver callbacks to the lower-half implementation.
 *   This must be called early in order to receive ADC event notifications.
 *
 ****************************************************************************/

static int adc_bind(struct adc_dev_s *dev,
                    const struct adc_callback_s *callback)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;

  DEBUGASSERT(priv != NULL);
  priv->cb = callback;
  return OK;
}

/****************************************************************************
 * Name: adc_wdog1_enable
 *
 * Description:
 *   Enable analog watchdog 1. Sets continuous and overrun mode. Turns on
 *   AWD1 interrupt and disables end of conversion interrupt.
 ****************************************************************************/

#ifdef ADC_HAVE_WDG1
static void adc_wdog1_enable(struct stm32_dev_s *priv)
{
  uint32_t regval;

  /* Initialize analog watchdog */

  regval = adc_getreg(priv, STM32_ADC_CFGR_OFFSET);
  regval |= ADC_CFGR_AWD1EN;
  adc_putreg(priv, STM32_ADC_CFGR_OFFSET, regval);

  /* Switch to analog watchdog interrupt */

  regval = adc_getreg(priv, STM32_ADC_IER_OFFSET);
  regval |= ADC_INT_AWD1;
  regval &= ~ADC_INT_EOC;
  adc_putreg(priv, STM32_ADC_IER_OFFSET, regval);
}

/****************************************************************************
 * Name: adc_wdog1_init
 *
 * Description:
 *   Initialize the ADC Watchdog 1 according to Kconfig options.
 ****************************************************************************/

static void adc_wdog1_init(struct stm32_dev_s *priv)
{
  uint32_t regval;

  regval = ((priv->wdg1_high_thresh << ADC_TR1_HT1_SHIFT)
            & ADC_TR1_HT1_MASK);
  regval |= ((priv->wdg1_low_thresh << ADC_TR1_LT1_SHIFT)
            & ADC_TR1_LT1_MASK);
  regval |= ((priv->wdg1_flt << ADC_TR1_AWDFILT_SHIFT)
            & ADC_TR1_AWDFILT_MASK);
  adc_putreg(priv, STM32_ADC_TR1_OFFSET, regval);

  regval = adc_getreg(priv, STM32_ADC_CFGR_OFFSET);
  if (priv->wdg1_single_chan == true)
    {
      regval |= ADC_CFGR_AWD1SGL;
      regval |= (ADC_CFGR_AWD1CH(priv->wdg1_chan)
                & ADC_CFGR_AWD1CH_MASK);
    }

  adc_putreg(priv, STM32_ADC_CFGR_OFFSET, regval);
}
#endif

/****************************************************************************
 * Name: adc_startconv
 *
 * Description:
 *   Start (or stop) the ADC conversion process
 *
 * Input Parameters:
 *   priv - A reference to the ADC block status
 *   enable - True: Start conversion
 *
 * Returned Value:
 *
 ****************************************************************************/

static void adc_startconv(struct stm32_dev_s *priv, bool enable)
{
  uint32_t regval;

  ainfo("enable: %d\n", enable ? 1 : 0);

  regval = adc_getreg(priv, STM32_ADC_CR_OFFSET);
  if (enable)
    {
      /* Start conversion of regular channels */

      regval |= ADC_CR_ADSTART;
    }
  else
    {
      /* Disable the conversion of regular channels */

      regval |= ADC_CR_ADSTP;
    }

  adc_putreg(priv, STM32_ADC_CR_OFFSET, regval);
}

/****************************************************************************
 * Name: adc_stopifstarted
 *
 * Description:
 *   Gracefully stop ADC regular and/or injected conversions if they are
 *   currently running.  This function writes ADSTP/JADSTP as needed,
 *   waits until ADSTART and/or JADSTART clear, then returns a mask of
 *   which conversion streams had been active so they can later be restarted.
 *
 * Input Parameters:
 *   priv - A reference to the ADC block status
 *
 * Returned Value:
 *   Bitmask of stopped conversion streams (ADC_CR_ADSTART and/or
 *   ADC_CR_JADSTART) indicating which were active before the stop.
 *
 ****************************************************************************/

static uint32_t adc_stopifstarted(struct stm32_dev_s *priv)
{
  uint32_t regval;
  uint32_t startbits;

  regval = adc_getreg(priv, STM32_ADC_CR_OFFSET);
  startbits = (regval & (ADC_CR_ADSTART | ADC_CR_JADSTART));

  regval &= ~(ADC_CR_ADSTART | ADC_CR_JADSTART);
  regval |= (ADC_CR_ADSTP | ADC_CR_JADSTP);
  adc_putreg(priv, STM32_ADC_CR_OFFSET, regval);

  while ((adc_getreg(priv, STM32_ADC_CR_OFFSET) &
          (ADC_CR_ADSTART | ADC_CR_JADSTART)) != 0);

  return startbits;
}

/****************************************************************************
 * Name: adc_startifstopped
 *
 * Description:
 *   Restart ADC conversions that were previously stopped by
 *   adc_stopifstarted().  The stopped_bits parameter should contain the
 *   ADSTART and/or JADSTART flags that were returned earlier, ensuring
 *   that only the streams that were active before are resumed.
 *
 * Input Parameters:
 *   priv        - A reference to the ADC block status
 *   stopped_bits - Bitmask of streams to restart (ADSTART/JADSTART)
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void adc_startifstopped(struct stm32_dev_s *priv,
                               uint8_t stopped_bits)
{
  uint32_t regval = adc_getreg(priv, STM32_ADC_CR_OFFSET);
  regval |= stopped_bits;
  adc_putreg(priv, STM32_ADC_CR_OFFSET, regval);
}

/****************************************************************************
 * Name: adc_rccreset
 *
 * Description:
 *   Deinitializes the ADCx peripheral registers to their default
 *   reset values. It could set all the ADCs configured.
 *
 * Input Parameters:
 *   priv - A reference to the ADC block status
 *   reset - Condition, set or reset
 *
 * Returned Value:
 *
 ****************************************************************************/

static void adc_rccreset(struct stm32_dev_s *priv, bool reset)
{
  irqstate_t flags;
  uint32_t regval;

  /* First must disable interrupts because the AHB2RSTR register is used by
   * several different drivers.
   */

  flags = enter_critical_section();

  /* Set or clear the adc reset bit in the AHB2 reset register */

  regval = getreg32(STM32_RCC_AHB2RSTR);

  if (reset)
    {
      regval |= RCC_AHB2RSTR_ADCRST;
    }
  else
    {
      regval &= ~RCC_AHB2RSTR_ADCRST;
    }

  putreg32(regval, STM32_RCC_AHB2RSTR);
  leave_critical_section(flags);
}

/****************************************************************************
 * Name: adc_shutdown
 *
 * Description:
 *   Disable the ADC.  This method is called when the ADC device is closed.
 *   This method reverses the operation the setup method.
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

static void adc_shutdown(struct adc_dev_s *dev)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;

  /* Stop the ADC */

  adc_startconv(priv, false);

  /* Disable ADC interrupts and detach the ADC interrupt handler */

  up_disable_irq(priv->irq);
  irq_detach(priv->irq);

  /* Disable and reset the ADC module */

  adc_reset(dev);

  priv->initialized = false;
}

/****************************************************************************
 * Name: adc_rxint
 *
 * Description:
 *   Call to enable or disable RX interrupts.
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

static void adc_rxint(struct adc_dev_s *dev, bool enable)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  uint32_t regval;

  ainfo("intf: %d enable: %d\n", priv->intf, enable ? 1 : 0);

  regval = adc_getreg(priv, STM32_ADC_IER_OFFSET);
  if (enable)
    {
      /* Enable end of conversion and overrun interrupts */

      regval |= ADC_INT_OVR;

      if (!priv->hasdma)
        {
          regval |= ADC_INT_EOC;
        }
    }
  else
    {
      /* Disable all interrupts */

      regval &= ~ADC_INT_MASK;
    }

  adc_putreg(priv, STM32_ADC_IER_OFFSET, regval);
}

/****************************************************************************
 * Name: adc_setupclock
 *
 ****************************************************************************/

static void adc_setupclock(struct stm32_dev_s *priv)
{
  uint32_t max_clock = 75000000;
  uint32_t setbits = 0;

#ifndef STM32_ADC_CLK_FREQUENCY
#error "board.h must define STM32_ADC_CLK_FREQUENCY"
#endif

  if (STM32_ADC_CLK_FREQUENCY <= max_clock)
    {
      setbits = ADC_CCR_PRESC_NOT_DIV;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 2 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV2;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 4 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV4;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 6 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV6;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 8 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV8;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 10 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV10;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 12 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV12;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 16 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV16;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 32 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV32;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 64 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV64;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 128 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV128;
    }
  else if (STM32_ADC_CLK_FREQUENCY / 256 <= max_clock)
    {
      setbits = ADC_CCR_PRESC_DIV256;
    }
  else
    {
      aerr("ERROR: source clock too high\n");
    }

  adc_modifyreg(priv, STM32_ADC_CCR_OFFSET, ADC_CCR_PRESC_MASK, setbits);
}

#ifdef ADC_HAVE_OVERSAMPLE
/****************************************************************************
 * Name: adc_oversample
 ****************************************************************************/

static void adc_oversample(struct adc_dev_s *dev)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;

  uint32_t clrbits = ADC_CFGR2_ROVSE | ADC_CFGR2_TROVS |
                     ADC_CFGR2_OVSR_MASK | ADC_CFGR2_OVSS_MASK;

  uint32_t setbits = ADC_CFGR2_ROVSE |
                     (priv->ovsr << ADC_CFGR2_OVSR_SHIFT) |
                     (priv->ovss << ADC_CFGR2_OVSS_SHIFT);

  setbits |= priv->trovs;

  adc_modifyreg(priv, STM32_ADC_CFGR2_OFFSET, clrbits, setbits);
}
#endif

/****************************************************************************
 * Name: adc_reset
 *
 * Description:
 *   Reset the ADC device.  Called early to initialize the hardware. This
 *   is called, before adc_setup() and on error conditions.
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

static void adc_reset(struct adc_dev_s *dev)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;

  ainfo("intf: ADC%d\n", priv->intf);

  /* Enable ADC reset state */

  adc_rccreset(priv, true);

  /* Release ADC from reset state */

  adc_rccreset(priv, false);
}

/****************************************************************************
 * Name: adc_restart_dma
 *
 * Description:
 *   Restarts DMA for the configured ADC DMA channel. This is used in
 *   one-shot mode, where DMA and ADC must be reconfigured and restarted
 *   after each completed transfer.
 *
 * Input Parameters:
 *   dev - adc device
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef ADC_HAVE_DMA
static void adc_restart_dma(struct adc_dev_s *dev)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  struct stm32_gpdma_cfg_s dmacfg;

  DEBUGASSERT(!priv->circular);

#ifdef ADC_HAVE_TIMER
  bool software_trigger = (priv->tbase == 0);
#else
  bool software_trigger = true;
#endif

  adc_dmacfg(priv, &dmacfg);
  stm32_dmasetup(priv->dma, &dmacfg);
  stm32_dmastart(priv->dma, adc_dmaconvcallback, dev, false);
  adc_startconv(priv, software_trigger);
}

/****************************************************************************
 * Name: adc_reset_dma
 *
 * Description:
 *   Reinitialize and restart the DMA stream used for ADC conversions.
 *   This stops the current DMA transfer, reloads the DMA configuration,
 *   and starts a new transfer with the proper callback and circular mode
 *   setting.
 *
 * Input Parameters:
 *   dev - Pointer to the ADC device structure. The private data is used
 *         to retrieve the DMA handle and configuration.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void adc_reset_dma(struct adc_dev_s *dev)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  struct stm32_gpdma_cfg_s dmacfg;

  adc_dmacfg(priv, &dmacfg);
  stm32_dmastop(priv->dma);
  stm32_dmasetup(priv->dma, &dmacfg);
  stm32_dmastart(priv->dma, adc_dmaconvcallback, dev, priv->circular);
}

/****************************************************************************
 * Name: adc_dmaconvcallback
 *
 * Description:
 *   Callback for DMA.  Called from the DMA transfer complete interrupt after
 *   all channels have been converted and transferred with DMA.
 *
 * Input Parameters:
 *
 *   handle - handle to DMA
 *   status -
 *   arg - adc device
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void adc_dmaconvcallback(DMA_HANDLE handle, uint8_t status, void *arg)
{
  struct adc_dev_s   *dev  = (struct adc_dev_s *)arg;
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;

  uint16_t conversion_count;
  uint16_t buffer_offset;
  int i;

  /* About Circular Mode
   * The size of r_dmabuffer and transfer size is doubled
   * half-transfer interrupts are enabled. Code should do this:
   * half-transfer int: read r_dmabuffer[0] through r_dmabuffer[conv_count-1]
   * transfer complete int: read r_dmabuffer[conv_count] to end of buffer.
   */

  /* Verify that the upper-half has bound its callback */

  if (priv->cb != NULL)
    {
      DEBUGASSERT(priv->cb->au_receive != NULL);

      if (status & DMA_STATUS_FATAL)
        {
          aerr("ADC DMA fatal error(s) — stopping DMA\n");

          if (status & DMA_STATUS_DTEF)
            {
              aerr("ADC DMA Error: DTEF (Data Transfer Error) occurred\n");
            }

          if (status & DMA_STATUS_ULEF)
            {
              aerr("ADC DMA Error: ULEF (Linked-list update error)\n");
            }

          if (status & DMA_STATUS_USEF)
            {
              aerr("ADC DMA Config Error: USEF (User Setting Error)\n");
            }

          stm32_dmastop(priv->dma);

          if (!priv->circular)
            {
              /* In non-circular mode, restart DMA and conversion */

              adc_restart_dma(dev);
            }

          return;
        }

      /* Circular Mode - Use second half of double size buffer on TCF */

      conversion_count = priv->rnchannels * priv->dmabatch;
      buffer_offset = (priv->circular) ? conversion_count : 0;

      /* Half-Transfer Interrupt enabled for circular mode only */

      if (status & DMA_STATUS_HTF && priv->circular)
        {
          for (i = 0; i < conversion_count; i++)
            {
              priv->cb->au_receive(dev,
                priv->chanlist[i % priv->rnchannels],
                priv->r_dmabuffer[i]);
            }
        }

      if (status & DMA_STATUS_TCF)
        {
          for (i = 0; i < conversion_count; i++)
            {
              priv->cb->au_receive(dev,
                priv->chanlist[i % priv->rnchannels],
                priv->r_dmabuffer[buffer_offset + i]);
            }
        }
    }

  /* Restart DMA for the next conversion series if in one-shot mode */

  if (!priv->circular)
    {
      adc_restart_dma(dev);
    }
}

/****************************************************************************
 * Name: adc_dmacfg
 *
 * Description:
 *   Generate the required DMA configuration structure for oneshot mode based
 *   on the ADC configuration.
 *
 * Input Parameters:
 *   priv     - ADC instance structure
 *   cfg      - DMA configuration structure
 *   circular - 0 = oneshot, 1 = circular
 *
 * Returned Value:
 *   None
 ****************************************************************************/

static void adc_dmacfg(struct stm32_dev_s *priv,
                       struct stm32_gpdma_cfg_s *cfg)
{
  const uint32_t sdw_log2 = 1;  /* Always 16-bit half-word for ADC_DR */

  cfg->src_addr   = priv->base + STM32_ADC_DR_OFFSET;
  cfg->dest_addr  = (uintptr_t)priv->r_dmabuffer;

  cfg->request    = (priv->base == STM32_ADC1_BASE)
                     ? GPDMA_REQ_ADC1
                     : GPDMA_REQ_ADC2;

  cfg->priority   = GPMDACFG_PRIO_LH;

  cfg->mode       = priv->circular ? GPDMACFG_MODE_CIRC : 0;

  cfg->ntransfers = (priv->cchannels * priv->dmabatch) << sdw_log2;
  cfg->ntransfers <<= (priv->circular ? 1 : 0);

  cfg->tr1        = (sdw_log2 << GPDMA_CXTR1_SDW_LOG2_SHIFT)
                  | (sdw_log2 << GPDMA_CXTR1_DDW_LOG2_SHIFT)
                  | GPDMA_CXTR1_DINC;  /* dest-inc, source fixed */
}
#endif

/****************************************************************************
 * Name: adc_setup
 *
 * Description:
 *   Configure the ADC. This method is called the first time that the ADC
 *   device is opened.  This will occur when the port is first opened.
 *   This setup includes configuring and attaching ADC interrupts.
 *   Interrupts are all disabled upon return.
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

static int adc_setup(struct adc_dev_s *dev)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
#ifdef ADC_HAVE_DMA
  struct stm32_gpdma_cfg_s dmacfg;
#endif
  int ret;
  irqstate_t flags;
  uint32_t clrbits;
  uint32_t setbits;

  /* Attach the ADC interrupt */

  ret = irq_attach(priv->irq, priv->isr, NULL);
  if (ret < 0)
    {
      ainfo("irq_attach failed: %d\n", ret);
      return ret;
    }

  flags = enter_critical_section();

  /* Make sure that the ADC device is in the powered up, reset state.
   * Since reset is shared between ADC1 and ADC2, don't reset one if the
   * other has already been reset. (We only need to worry about this if both
   * ADC1 and ADC2 are enabled.)
   */

#if defined(CONFIG_STM32H5_ADC1) && defined(CONFIG_STM32H5_ADC2)
  if ((dev == &g_adcdev1 &&
      !((struct stm32_dev_s *)g_adcdev2.ad_priv)->initialized) ||
     (dev == &g_adcdev2 &&
      !((struct stm32_dev_s *)g_adcdev1.ad_priv)->initialized))
#endif
    {
      adc_reset(dev);
    }

  adc_putreg(priv, STM32_ADC_SMPR1_OFFSET, priv->smpr1);
  adc_putreg(priv, STM32_ADC_SMPR2_OFFSET, priv->smpr2);
  adc_putreg(priv, STM32_ADC_DIFSEL_OFFSET, priv->difsel);

  /* Set the resolution of the conversion. */

  clrbits = ADC_CFGR_RES_MASK | ADC_CFGR_DMACFG | ADC_CFGR_DMAEN;
  setbits = (priv->resolution << ADC_CFGR_RES_SHIFT) & ADC_CFGR_RES_MASK;

#ifdef ADC_HAVE_DMA
  if (priv->hasdma)
    {
      /* Enable One-shot or Circular DMA.
       * WARNING: This doesn't work in dual-ADC modes. [RM0481] ADC_CFGR
       * register description (pg. 1122) - "In dual-ADC modes, this bit is
       * not relevant and replaced by control bit DMACFG of the ADC_CCR
       * register"
       */

      setbits |= ADC_CFGR_DMAEN;

      if (priv->circular)
        {
          setbits |= ADC_CFGR_OVRMOD; /* overwrite on overrun */
          setbits |= ADC_CFGR_DMACFG;
          setbits |= ADC_CFGR_CONT;
        }
      else
        {
          clrbits |= ADC_CFGR_DMACFG;
          clrbits |= ADC_CFGR_CONT;
          clrbits |= ADC_CFGR_OVRMOD; /* keep DR for non-DMA/sparse reads */
        }
    }
  else
#endif
    {
      clrbits |= ADC_CFGR_CONT;
      clrbits |= ADC_CFGR_OVRMOD; /* keep DR for non-DMA/sparse reads */
    }

  /* Disable external trigger for regular channels */

  clrbits |= ADC_CFGR_EXTEN_MASK;
  setbits |= ADC_CFGR_EXTEN_NONE;

  /* Set CFGR configuration */

  adc_modifyreg(priv, STM32_ADC_CFGR_OFFSET, clrbits, setbits);

  /* Set CFGR2 configuration to align right no oversample */

  clrbits = ADC_CFGR2_ROVSE | ADC_CFGR2_JOVSE | ADC_CFGR2_OVSS_MASK \
          | ADC_CFGR2_OVSR_MASK;
  setbits = 0;

  adc_modifyreg(priv, STM32_ADC_CFGR2_OFFSET, clrbits, setbits);

  /* Configuration of the channel conversions */

  adc_set_ch(dev, 0);

  /* ADC CCR configuration */

  clrbits = ADC_CCR_PRESC_MASK | ADC_CCR_VREFEN |
            ADC_CCR_TSEN | ADC_CCR_VBATEN;
  setbits = ADC_CCR_CKMODE_ASYCH;

  adc_internal(priv, &setbits);

  adc_modifyregm(priv, STM32_ADC_CCR_OFFSET, clrbits, setbits);

  adc_setupclock(priv);

#ifdef ADC_HAVE_OVERSAMPLE
  if (priv->oversample)
    {
      adc_oversample(dev);
    }
#endif

  leave_critical_section(flags);

#ifdef ADC_HAVE_DMA

  /* Enable DMA */

  if (priv->hasdma)
    {
      /* Stop and free DMA if it was started before */

      if (priv->dma != NULL)
        {
          stm32_dmastop(priv->dma);
          stm32_dmafree(priv->dma);
        }

      priv->dma = stm32_dmachannel(GPDMA_TTYPE_P2M);

      adc_dmacfg(priv, &dmacfg);

      stm32_dmasetup(priv->dma, &dmacfg);

      stm32_dmastart(priv->dma, adc_dmaconvcallback, dev, priv->circular);
    }
#endif

#ifdef ADC_HAVE_WDG1
  if (priv->wdg1_enable)
    {
      adc_wdog1_init(priv);
      adc_wdog1_enable(priv);
    }
#endif

  /* Set ADEN to wake up the ADC from Power Down. */

  adc_enable(priv);

#ifdef ADC_HAVE_TIMER
  if (priv->tbase != 0)
    {
      ret = adc_timinit(priv);
      if (ret < 0)
        {
          aerr("ERROR: adc_timinit failed: %d\n", ret);
        }

      adc_startconv(priv, ret < 0 ? false : true);
    }
#endif

  ainfo("ISR:   0x%08" PRIx32 " CR:    0x%08" PRIx32 " "
        "CFGR:  0x%08" PRIx32 " CFGR2: 0x%08" PRIx32 "\n",
        adc_getreg(priv, STM32_ADC_ISR_OFFSET),
        adc_getreg(priv, STM32_ADC_CR_OFFSET),
        adc_getreg(priv, STM32_ADC_CFGR_OFFSET),
        adc_getreg(priv, STM32_ADC_CFGR2_OFFSET));
  ainfo("SQR1:  0x%08" PRIx32 " SQR2:  0x%08" PRIx32 " "
        "SQR3:  0x%08" PRIx32 " SQR4:  0x%08" PRIx32 "\n",
        adc_getreg(priv, STM32_ADC_SQR1_OFFSET),
        adc_getreg(priv, STM32_ADC_SQR2_OFFSET),
        adc_getreg(priv, STM32_ADC_SQR3_OFFSET),
        adc_getreg(priv, STM32_ADC_SQR4_OFFSET));
  ainfo("CCR:   0x%08" PRIx32 "\n", adc_getregm(priv, STM32_ADC_CCR_OFFSET));

  ainfo("Enable the ADC interrupt: irq=%d\n", priv->irq);
  up_enable_irq(priv->irq);

  priv->initialized = true;

  return ret;
}

/****************************************************************************
 * Name: adc_sqrbits
 ****************************************************************************/

static uint32_t adc_sqrbits(struct stm32_dev_s *priv, int first,
                            int last, int offset)
{
  uint32_t bits = 0;
  int i;

  for (i = first - 1;
       i < priv->rnchannels && i < last;
       i++, offset += ADC_SQ_OFFSET)
    {
      bits |= (uint32_t)priv->chanlist[i] << offset;
    }

  return bits;
}

/****************************************************************************
 * Name: adc_internal
 ****************************************************************************/

static bool adc_internal(struct stm32_dev_s * priv, uint32_t *adc_ccr)
{
  int i;
  bool internal = false;

  if (priv->intf == 1)
    {
      for (i = 0; i < priv->rnchannels; i++)
        {
            switch (priv->chanlist[i])
              {
                case 16:
                  *adc_ccr |= ADC_CCR_TSEN;
                  internal = true;
                  break;

                case 17:
                  *adc_ccr |= ADC_CCR_VREFEN;
                  internal = true;
                  break;
              }
        }
    }
  else if (priv->intf == 2)
    {
      for (i = 0; i < priv->rnchannels; i++)
        {
          switch (priv->chanlist[i])
            {
              case 16:
                *adc_ccr |= ADC_CCR_VBATEN;
                internal = true;
                break;

              case 17:

                /* Measuring VDDCORE requires option bit to be set */

                adc_putreg(priv, STM32_ADC_OR_OFFSET, ADC_OR_OP0);
                internal = true;
                break;
            }
        }
    }

  return internal;
}

/****************************************************************************
 * Name: adc_set_ch
 *
 * Description:
 *   Sets the ADC channel.
 *
 * Input Parameters:
 *   dev - pointer to device structure used by the driver
 *   ch  - ADC channel number + 1. 0 reserved for all configured channels
 *
 * Returned Value:
 *   int - errno
 *
 ****************************************************************************/

static int adc_set_ch(struct adc_dev_s *dev, uint8_t ch)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  uint32_t bits;
  int i;

  if (ch == 0)
    {
      priv->current   = 0;
      priv->rnchannels = priv->cchannels;
    }
  else
    {
      for (i = 0; i < priv->cchannels &&
           priv->chanlist[i] != ch - 1; i++);

      if (i >= priv->cchannels)
        {
          return -ENODEV;
        }

      priv->current   = i;
      priv->rnchannels = 1;
    }

  DEBUGASSERT(priv->rnchannels <= CONFIG_STM32H5_ADC_MAX_SAMPLES);

  bits = adc_sqrbits(priv, ADC_SQR4_FIRST, ADC_SQR4_LAST,
                     ADC_SQR4_SQ_OFFSET);
  adc_modifyreg(priv, STM32_ADC_SQR4_OFFSET, ~ADC_SQR4_RESERVED, bits);

  bits = adc_sqrbits(priv, ADC_SQR3_FIRST, ADC_SQR3_LAST,
                     ADC_SQR3_SQ_OFFSET);
  adc_modifyreg(priv, STM32_ADC_SQR3_OFFSET, ~ADC_SQR3_RESERVED, bits);

  bits = adc_sqrbits(priv, ADC_SQR2_FIRST, ADC_SQR2_LAST,
                     ADC_SQR2_SQ_OFFSET);
  adc_modifyreg(priv, STM32_ADC_SQR2_OFFSET, ~ADC_SQR2_RESERVED, bits);

  bits = ((uint32_t)priv->rnchannels - 1) << ADC_SQR1_L_SHIFT |
         adc_sqrbits(priv, ADC_SQR1_FIRST, ADC_SQR1_LAST,
                     ADC_SQR1_SQ_OFFSET);
  adc_modifyreg(priv, STM32_ADC_SQR1_OFFSET, ~ADC_SQR1_RESERVED, bits);

  return OK;
}

#ifdef ADC_HAVE_OVERSAMPLE
/****************************************************************************
 * Name: adc_ioc_set_oversample
 *
 * Description:
 *   For STM32G0 and STM32L0: Configure hardware oversampling via CFGR2.
 *
 * Input:
 *   dev - pointer to the ADC device
 *   arg - Packed 32-bit value that matches CFGR2 layout for OVSE, TOVS,
 *         OVSR[2:0] and OVSS[3:0].
 *
 *         Bit fields (match ADC_CFGR2 register layout):
 *           [0]     = OVSE  (enable oversampling)
 *           [1]     = TOVS  (triggered oversampling)
 *           [4:2]   = OVSR  (ratio: 000=2x, ..., 111=256x)
 *           [9:5]   = OVSS  (right shift: 00000=no shift, ..., 11111=31-bit)
 *
 * Returned Value:
 *   OK (0) on success
 *
 ****************************************************************************/

static int adc_ioc_set_oversample(struct adc_dev_s *dev, uint32_t arg)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  uint32_t clrbits;
  uint32_t setbits;

  /* Mask out the oversampling-related fields from CFGR2:
   * OVSE | TOVS | OVSR[2:0] | OVSS[3:0]
   */

  clrbits = ADC_CFGR2_ROVSE     |
            ADC_CFGR2_TROVS     |
            ADC_CFGR2_OVSR_MASK |
            ADC_CFGR2_OVSS_MASK;

  setbits = arg & (ADC_CFGR2_ROVSE     |
                   ADC_CFGR2_TROVS     |
                   ADC_CFGR2_OVSR_MASK |
                   ADC_CFGR2_OVSS_MASK);

  adc_modifyreg(priv, STM32_ADC_CFGR2_OFFSET, clrbits, setbits);
  return OK;
}

#endif

/****************************************************************************
 * Name: adc_ioctl
 *
 * Description:
 *   All ioctl calls will be routed through this method.
 *
 * Input Parameters:
 *   dev - pointer to device structure used by the driver
 *   cmd - command
 *   arg - arguments passed with command
 *
 * Returned Value:
 *
 ****************************************************************************/

static int adc_ioctl(struct adc_dev_s *dev, int cmd, unsigned long arg)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  int ret = OK;

  switch (cmd)
    {
      case ANIOC_TRIGGER:
        {
          adc_startconv(priv, true);
        }
        break;

      case ANIOC_GET_NCHANNELS:
        {
          /* Return the number of configured channels */

          ret = priv->cchannels;
        }
        break;

#ifdef ADC_HAVE_WDG1
  uint32_t regval;
  uint32_t startbits;
  uint32_t tmp;
     case ANIOC_WDOG_UPPER: /* Set watchdog upper threshold */
        {
          startbits = adc_stopifstarted(priv);
          regval = adc_getreg(priv, STM32_ADC_TR1_OFFSET);

          /* Verify new upper threshold greater than lower threshold */

          tmp = (regval & ADC_TR1_LT1_MASK) >> ADC_TR1_LT1_SHIFT;
          if (arg < tmp)
            {
              ret = -EINVAL;
              break;
            }

          /* Set the watchdog threshold register */

          regval = ((arg << ADC_TR1_HT1_SHIFT) & ADC_TR1_HT1_MASK);
          adc_putreg(priv, STM32_ADC_TR1_OFFSET, regval);

          /* Ensure analog watchdog is enabled */

          adc_wdog1_enable(priv);
#ifdef ADC_HAVE_DMA
          if (priv->hasdma)
            {
              adc_reset_dma(dev);
            }
#endif

          adc_startifstopped(priv, startbits);
        }

        break;

      case ANIOC_WDOG_LOWER: /* Set watchdog lower threshold */
        {
          startbits = adc_stopifstarted(priv);
          regval = adc_getreg(priv, STM32_ADC_TR1_OFFSET);

          /* Verify new lower threshold less than upper threshold */

          tmp = (regval & ADC_TR1_HT1_MASK) >> ADC_TR1_HT1_SHIFT;
          if (arg > tmp)
            {
              ret = -EINVAL;
              break;
            }

          /* Set the watchdog threshold register */

          regval = ((arg << ADC_TR1_LT1_SHIFT) & ADC_TR1_LT1_MASK);
          adc_putreg(priv, STM32_ADC_TR1_OFFSET, regval);

          /* Ensure analog watchdog is enabled */

          adc_wdog1_enable(priv);
#ifdef ADC_HAVE_DMA
          if (priv->hasdma)
            {
              adc_reset_dma(dev);
            }
#endif

          adc_startifstopped(priv, startbits);
        }

        break;
#endif

#ifdef ADC_HAVE_OVERSAMPLE
      case ANIOC_SET_OVERSAMPLE:
        {
          ret = adc_ioc_set_oversample(dev, arg);
          break;
        }
#endif

      default:
        aerr("ERROR: Unknown cmd: %d\n", cmd);
        ret = -ENOTTY;
        break;
    }

  return ret;
}

/****************************************************************************
 * Name: adc_interrupt
 *
 * Description:
 *   Common ADC interrupt handler.
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

static int adc_interrupt(struct adc_dev_s *dev, uint32_t adcisr)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  int32_t value;

#ifdef ADC_HAVE_WDG
  uint32_t awd_mask = adcisr & (ADC_INT_AWD1 | ADC_INT_AWD2 | ADC_INT_AWD3);
  uint32_t regval;
  if (awd_mask != 0)
    {
      regval = adc_getreg(priv, STM32_ADC_IER_OFFSET);
      regval &= ~(awd_mask);
      adc_putreg(priv, STM32_ADC_IER_OFFSET, regval);

      if ((adcisr & ADC_INT_AWD1) != 0)
        {
          awarn("WARNING: Analog Watchdog 1 out of range!\n");
        }

      if ((adcisr & ADC_INT_AWD2) != 0)
        {
          awarn("WARNING: Analog Watchdog 2 out of range!\n");
        }

      if ((adcisr & ADC_INT_AWD3) != 0)
        {
          awarn("WARNING: Analog Watchdog 3 out of range!\n");
        }

      adc_putreg(priv, STM32_ADC_ISR_OFFSET, awd_mask);
    }
#endif

  /* OVR: Overrun */

  if ((adcisr & ADC_INT_OVR) != 0)
    {
      /* In case of a missed ISR - due to interrupt saturation -
       * the upper half needs to be informed to terminate properly.
       */

      awarn("WARNING: Overrun has occurred!\n");

      /* To make use of already sampled data the conversion needs to be
       * stopped first before reading out the data register.
       */

      adc_startconv(priv, false);

      while ((adc_getreg(priv, STM32_ADC_CR_OFFSET) & ADC_CR_ADSTART) != 0);

      /* Verify that the upper-half driver has bound its callback functions */

      if ((priv->cb != NULL) && (priv->cb->au_reset != NULL))
        {
          /* Notify upper-half driver about the overrun */

          priv->cb->au_reset(dev);
        }

      adc_putreg(priv, STM32_ADC_ISR_OFFSET, ADC_INT_OVR);
    }

  /* EOC: End of conversion */

  if ((adcisr & ADC_INT_EOC) != 0)
    {
      /* Read from the ADC_DR register until 8 stage FIFO is empty.
       * The FIFO is first mentioned in STM32H5 Reference Manual
       * rev. 7, though, not yet indicated in the block diagram!
       */

      do
        {
          /* Read the converted value and clear EOC bit
           * (It is cleared by reading the ADC_DR)
           */

          value = adc_getreg(priv, STM32_ADC_DR_OFFSET) & ADC_DR_MASK;

          /* Verify that the upper-half driver has bound its
           * callback functions
           */

          if (priv->cb != NULL)
            {
              /* Hand the ADC data to the ADC driver.  The ADC receive()
               * method accepts 3 parameters:
               *
               * 1) The first is the ADC device instance for this ADC block.
               * 2) The second is the channel number for the data, and
               * 3) The third is the converted data for the channel.
               */

              DEBUGASSERT(priv->cb->au_receive != NULL);
              priv->cb->au_receive(dev, priv->chanlist[priv->current],
                                   value);
            }

          /* Set the channel number of the next channel that will
           * complete conversion
           */

          priv->current++;

          if (priv->current >= priv->rnchannels)
            {
              /* Restart the conversion sequence from the beginning */

              priv->current = 0;
            }
        }
      while ((adc_getreg(priv, STM32_ADC_ISR_OFFSET) & ADC_INT_EOC) != 0);

      /* We don't add EOC to the bits to clear. It will cause a race
       * condition.  EOC should only be cleared by reading the ADC_DR
       */
    }

  return OK;
}

/****************************************************************************
 * Name: adc12_interrupt
 *
 * Description:
 *   ADC1/2 interrupt handler
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

#if defined(CONFIG_STM32H5_ADC1) || defined(CONFIG_STM32H5_ADC2)
static int adc12_interrupt(int irq, void *context, void *arg)
{
  uint32_t regval;
  uint32_t pending;

#ifdef CONFIG_STM32H5_ADC1
  regval  = getreg32(STM32_ADC1_ISR);
  pending = regval & ADC_INT_MASK;
  if (pending != 0)
    {
      adc_interrupt(&g_adcdev1, regval);
    }
#endif

#ifdef CONFIG_STM32H5_ADC2
  regval  = getreg32(STM32_ADC2_ISR);
  pending = regval & ADC_INT_MASK;
  if (pending != 0)
    {
      adc_interrupt(&g_adcdev2, regval);
    }
#endif

  return OK;
}
#endif

#ifdef ADC_HAVE_TIMER
/****************************************************************************
 * Name: tim_getreg
 *
 * Description:
 *   Read the value of an ADC timer register.
 *
 * Input Parameters:
 *   priv   - A reference to the ADC block status
 *   offset - The offset to the register to read
 *
 * Returned Value:
 *   The current contents of the specified register
 *
 ****************************************************************************/

static uint16_t tim_getreg(struct stm32_dev_s *priv, int offset)
{
  return getreg16(priv->tbase + offset);
}

/****************************************************************************
 * Name: tim_putreg
 *
 * Description:
 *   Write a value to an ADC timer register.
 *
 * Input Parameters:
 *   priv   - A reference to the ADC block status
 *   offset - The offset to the register to write to
 *   value  - The value to write to the register
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void tim_putreg(struct stm32_dev_s *priv, int offset,
                       uint16_t value)
{
  putreg16(value, priv->tbase + offset);
}

/****************************************************************************
 * Name: tim_modifyreg
 *
 * Description:
 *   Modify the value of an ADC timer register (not atomic).
 *
 * Input Parameters:
 *   priv    - A reference to the ADC block status
 *   offset  - The offset to the register to modify
 *   clrbits - The bits to clear
 *   setbits - The bits to set
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void tim_modifyreg(struct stm32_dev_s *priv, int offset,
                          uint16_t clrbits, uint16_t setbits)
{
  tim_putreg(priv, offset, (tim_getreg(priv, offset) & ~clrbits) | setbits);
}

/****************************************************************************
 * Name: tim_modifyreg32
 *
 * Description:
 *   Modify the value of an ADC timer register (not atomic).
 *
 * Input Parameters:
 *   priv    - A reference to the ADC block status
 *   offset  - The offset to the register to modify
 *   clrbits - The bits to clear
 *   setbits - The bits to set
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void tim_modifyreg32(struct stm32_dev_s *priv, int offset,
                            uint32_t clrbits, uint32_t setbits)
{
  uint32_t addr = priv->tbase + offset;
  putreg32((getreg32(addr) & ~clrbits) | setbits, addr);
}

/****************************************************************************
 * Name: tim_dumpregs
 *
 * Description:
 *   Dump all timer registers.
 *
 * Input Parameters:
 *   priv - A reference to the ADC block status
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void tim_dumpregs(struct stm32_dev_s *priv, const char *msg)
{
  ainfo("%s:\n", msg);
  ainfo("  CR1: %04x CR2:  %04x SMCR:  %04x DIER:  %04x\n",
        tim_getreg(priv, STM32_GTIM_CR1_OFFSET),
        tim_getreg(priv, STM32_GTIM_CR2_OFFSET),
        tim_getreg(priv, STM32_GTIM_SMCR_OFFSET),
        tim_getreg(priv, STM32_GTIM_DIER_OFFSET));
  ainfo("   SR: %04x EGR:  0000 CCMR1: %04x CCMR2: %04x\n",
        tim_getreg(priv, STM32_GTIM_SR_OFFSET),
        tim_getreg(priv, STM32_GTIM_CCMR1_OFFSET),
        tim_getreg(priv, STM32_GTIM_CCMR2_OFFSET));
  ainfo(" CCER: %04x CNT:  %04x PSC:   %04x ARR:   %04x\n",
        tim_getreg(priv, STM32_GTIM_CCER_OFFSET),
        tim_getreg(priv, STM32_GTIM_CNT_OFFSET),
        tim_getreg(priv, STM32_GTIM_PSC_OFFSET),
        tim_getreg(priv, STM32_GTIM_ARR_OFFSET));
  ainfo(" CCR1: %04x CCR2: %04x CCR3:  %04x CCR4:  %04x\n",
        tim_getreg(priv, STM32_GTIM_CCR1_OFFSET),
        tim_getreg(priv, STM32_GTIM_CCR2_OFFSET),
        tim_getreg(priv, STM32_GTIM_CCR3_OFFSET),
        tim_getreg(priv, STM32_GTIM_CCR4_OFFSET));

  if (priv->tbase == STM32_TIM1_BASE || priv->tbase == STM32_TIM8_BASE)
    {
      ainfo("  RCR: %04x BDTR: %04x DCR:   %04x DMAR:  %04x\n",
            tim_getreg(priv, STM32_ATIM_RCR_OFFSET),
            tim_getreg(priv, STM32_ATIM_BDTR_OFFSET),
            tim_getreg(priv, STM32_ATIM_DCR_OFFSET),
            tim_getreg(priv, STM32_ATIM_DMAR_OFFSET));
    }
  else
    {
      ainfo("  DCR: %04x DMAR: %04x\n",
            tim_getreg(priv, STM32_GTIM_DCR_OFFSET),
            tim_getreg(priv, STM32_GTIM_DMAR_OFFSET));
    }
}

/****************************************************************************
 * Name: adc_timstart
 *
 * Description:
 *   Start (or stop) the timer counter
 *
 * Input Parameters:
 *   priv - A reference to the ADC block status
 *   enable - True: Start conversion
 *
 * Returned Value:
 *
 ****************************************************************************/

static void adc_timstart(struct stm32_dev_s *priv, bool enable)
{
  ainfo("enable: %d\n", enable ? 1 : 0);

  if (enable)
    {
      /* Start the counter */

      tim_modifyreg(priv, STM32_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN);
    }
  else
    {
      /* Disable the counter */

      tim_modifyreg(priv, STM32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0);
    }
}

/****************************************************************************
 * Name: adc_timinit
 *
 * Description:
 *   Initialize the timer that drivers the ADC sampling for this channel
 *   using the pre-calculated timer divider definitions.
 *
 * Input Parameters:
 *   priv - A reference to the ADC block status
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure.
 *
 ****************************************************************************/

static int adc_timinit(struct stm32_dev_s *priv)
{
  uint32_t prescaler;
  uint32_t reload;
  uint32_t timclk;

  uint16_t clrbits = 0;
  uint16_t setbits = 0;
  uint16_t cr2;
  uint16_t ccmr1;
  uint16_t ccmr2;
  uint16_t ocmode1;
  uint16_t ocmode2;
  uint16_t ccenable;
  uint16_t ccer;
  uint16_t egr;

  /* If the timer base address is zero, then this ADC was not configured to
   * use a timer.
   */

  if (priv->tbase == 0)
    {
      return ERROR;
    }

  /* EXTSEL selection: These bits select the external event used to trigger
   * the start of conversion of a regular group.  NOTE:
   *
   * - The position with of the EXTSEL field varies from one STM32 MCU
   *   to another.
   * - The width of the EXTSEL field varies from one STM32 MCU to another.
   * - The value in priv->extsel is already shifted into the correct bit
   *   position.
   */

  ainfo("Initializing timers extsel = 0x%08" PRIx32 "\n", priv->extsel);

  adc_modifyreg(priv, STM32_ADC_CFGR_OFFSET,
                ADC_CFGR_EXTEN_MASK | ADC_CFGR_EXTSEL_MASK,
                ADC_CFGR_EXTEN_RISING | priv->extsel);

  /* Configure the timer channel to drive the ADC */

  /* Enable Timer clocking */

  modifyreg32(priv->trcc_enr, 0, priv->trcc_en);

  /* Calculate optimal values for the timer prescaler and for the timer
   * reload register.  If freq is the desired frequency, then
   *
   *   reload = timclk / freq
   *   reload = (pclck / prescaler) / freq
   *
   * There are many solutions to do this, but the best solution will be the
   * one that has the largest reload value and the smallest prescaler value.
   * That is the solution that should give us the most accuracy in the timer
   * control.  Subject to:
   *
   *   0 <= prescaler  <= 65536
   *   1 <= reload <= 65535
   *
   * So ( prescaler = pclck / 65535 / freq ) would be optimal.
   */

  prescaler = (priv->pclck / priv->freq + 65534) / 65535;

  /* We need to decrement the prescaler value by one, but only, the value
   * does not underflow.
   */

  if (prescaler < 1)
    {
      awarn("WARNING: Prescaler underflowed.\n");
      prescaler = 1;
    }

  /* Check for overflow */

  else if (prescaler > 65536)
    {
      awarn("WARNING: Prescaler overflowed.\n");
      prescaler = 65536;
    }

  timclk = priv->pclck / prescaler;

  reload = timclk / priv->freq;
  if (reload < 1)
    {
      awarn("WARNING: Reload value underflowed.\n");
      reload = 1;
    }

  else if (reload > 65535)
    {
      awarn("WARNING: Reload value overflowed.\n");
      reload = 65535;
    }

  /* Disable the timer until we get it configured */

  adc_timstart(priv, false);

  /* Set up the timer CR1 register.
   *
   * Select the Counter Mode == count up:
   *
   * ATIM_CR1_EDGE: The counter counts up or down depending on the
   *                direction bit(DIR).
   * ATIM_CR1_DIR: 0: count up, 1: count down
   *
   * Set the clock division to zero for all
   */

  clrbits = GTIM_CR1_DIR | GTIM_CR1_CMS_MASK | GTIM_CR1_CKD_MASK;
  setbits = GTIM_CR1_EDGE;
  tim_modifyreg(priv, STM32_GTIM_CR1_OFFSET, clrbits, setbits);

  /* Set the reload and prescaler values */

  tim_putreg(priv, STM32_GTIM_PSC_OFFSET, prescaler - 1);
  tim_putreg(priv, STM32_GTIM_ARR_OFFSET, reload);

  /* Clear the advanced timers repetition counter in TIM1 */

  if (priv->tbase == STM32_TIM1_BASE || priv->tbase == STM32_TIM8_BASE)
    {
      tim_putreg(priv, STM32_ATIM_RCR_OFFSET, 0);
      tim_putreg(priv, STM32_ATIM_BDTR_OFFSET, ATIM_BDTR_MOE); /* Check me */
    }

  /* TIMx event generation: Bit 0 UG: Update generation */

  tim_putreg(priv, STM32_GTIM_EGR_OFFSET, GTIM_EGR_UG);

  /* Handle channel specific setup */

  ocmode1 = 0;
  ocmode2 = 0;

  switch (priv->trigger)
    {
      case 0: /* TimerX CC1 event */
        {
          ccenable = ATIM_CCER_CC1E;
          ocmode1  = (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC1S_SHIFT) |
                     (ATIM_CCMR_MODE_PWM1 << ATIM_CCMR1_OC1M_SHIFT) |
                     ATIM_CCMR1_OC1PE;

          /* Set the event CC1 */

          egr      = ATIM_EGR_CC1G;

          /* Set the duty cycle by writing to the CCR register for this
           * channel
           */

          tim_putreg(priv, STM32_GTIM_CCR1_OFFSET, (uint16_t)(reload >> 1));
        }
        break;

      case 1: /* TimerX CC2 event */
        {
          ccenable = ATIM_CCER_CC2E;
          ocmode1  = (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC2S_SHIFT) |
                     (ATIM_CCMR_MODE_PWM1 << ATIM_CCMR1_OC2M_SHIFT) |
                     ATIM_CCMR1_OC2PE;

          /* Set the event CC2 */

          egr      = ATIM_EGR_CC2G;

          /* Set the duty cycle by writing to the CCR register for this
           * channel
           */

          tim_putreg(priv, STM32_GTIM_CCR2_OFFSET, (uint16_t)(reload >> 1));
        }
        break;

      case 2: /* TimerX CC3 event */
        {
          ccenable = ATIM_CCER_CC3E;
          ocmode2  = (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR2_CC3S_SHIFT) |
                     (ATIM_CCMR_MODE_PWM1 << ATIM_CCMR2_OC3M_SHIFT) |
                     ATIM_CCMR2_OC3PE;

          /* Set the event CC3 */

          egr      = ATIM_EGR_CC3G;

          /* Set the duty cycle by writing to the CCR register for this
           * channel
           */

          tim_putreg(priv, STM32_GTIM_CCR3_OFFSET, (uint16_t)(reload >> 1));
        }
        break;

      case 3: /* TimerX CC4 event */
        {
          ccenable = ATIM_CCER_CC4E;
          ocmode2  = (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR2_CC4S_SHIFT) |
                     (ATIM_CCMR_MODE_PWM1 << ATIM_CCMR2_OC4M_SHIFT) |
                     ATIM_CCMR2_OC4PE;

          /* Set the event CC4 */

          egr      = ATIM_EGR_CC4G;

          /* Set the duty cycle by writing to the CCR register for this
           * channel
           */

          tim_putreg(priv, STM32_GTIM_CCR4_OFFSET, (uint16_t)(reload >> 1));
        }
        break;

      case 4: /* TimerX TRGO event */
        {
          /* Set the event TRGO */

          ccenable = 0;
          egr      = GTIM_EGR_TG;

          tim_modifyreg(priv, STM32_GTIM_CR2_OFFSET, clrbits,
                        GTIM_CR2_MMS_UPDATE);
        }
        break;

      case 5: /* TimerX TRGO2 event */
        {
          /* Set the event TRGO2 */

          ccenable = 0;
          egr      = GTIM_EGR_TG;

          tim_modifyreg32(priv, STM32_GTIM_CR2_OFFSET, clrbits,
                          ATIM_CR2_MMS2_UPDATE);
        }
        break;

      default:
        aerr("ERROR: No such trigger: %d\n", priv->trigger);
        return -EINVAL;
    }

  /* Disable the Channel by resetting the CCxE Bit in the CCER register */

  ccer = tim_getreg(priv, STM32_GTIM_CCER_OFFSET);
  ccer &= ~ccenable;
  tim_putreg(priv, STM32_GTIM_CCER_OFFSET, ccer);

  /* Fetch the CR2, CCMR1, and CCMR2 register (already have ccer) */

  cr2   = tim_getreg(priv, STM32_GTIM_CR2_OFFSET);
  ccmr1 = tim_getreg(priv, STM32_GTIM_CCMR1_OFFSET);
  ccmr2 = tim_getreg(priv, STM32_GTIM_CCMR2_OFFSET);

  /* Reset the Output Compare Mode Bits and set the select output compare
   * mode
   */

  ccmr1 &= ~(ATIM_CCMR1_CC1S_MASK | ATIM_CCMR1_OC1M_MASK | ATIM_CCMR1_OC1PE |
             ATIM_CCMR1_CC2S_MASK | ATIM_CCMR1_OC2M_MASK | ATIM_CCMR1_OC2PE);
  ccmr2 &= ~(ATIM_CCMR2_CC3S_MASK | ATIM_CCMR2_OC3M_MASK | ATIM_CCMR2_OC3PE |
             ATIM_CCMR2_CC4S_MASK | ATIM_CCMR2_OC4M_MASK | ATIM_CCMR2_OC4PE);
  ccmr1 |= ocmode1;
  ccmr2 |= ocmode2;

  /* Reset the output polarity level of all channels (selects high
   * polarity)
   */

  ccer &= ~(ATIM_CCER_CC1P | ATIM_CCER_CC2P |
            ATIM_CCER_CC3P | ATIM_CCER_CC4P);

  /* Enable the output state of the selected channel (only) */

  ccer &= ~(ATIM_CCER_CC1E | ATIM_CCER_CC2E |
            ATIM_CCER_CC3E | ATIM_CCER_CC4E);
  ccer |= ccenable;

  if (priv->tbase == STM32_TIM1_BASE || priv->tbase == STM32_TIM8_BASE)
    {
      /* Reset output N polarity level, output N state, output compare state,
       * output compare N idle state.
       */

      ccer &= ~(ATIM_CCER_CC1NE | ATIM_CCER_CC1NP |
                ATIM_CCER_CC2NE | ATIM_CCER_CC2NP |
                ATIM_CCER_CC3NE | ATIM_CCER_CC3NP |
                ATIM_CCER_CC4NP);

      /* Reset the output compare and output compare N IDLE State */

      cr2 &= ~(ATIM_CR2_OIS1 | ATIM_CR2_OIS1N |
               ATIM_CR2_OIS2 | ATIM_CR2_OIS2N |
               ATIM_CR2_OIS3 | ATIM_CR2_OIS3N |
               ATIM_CR2_OIS4);
    }
  else
    {
      ccer &= ~(GTIM_CCER_CC1NP | GTIM_CCER_CC2NP | GTIM_CCER_CC3NP);
    }

  /* Save the modified register values */

  tim_putreg(priv, STM32_GTIM_CR2_OFFSET, cr2);
  tim_putreg(priv, STM32_GTIM_CCMR1_OFFSET, ccmr1);
  tim_putreg(priv, STM32_GTIM_CCMR2_OFFSET, ccmr2);
  tim_putreg(priv, STM32_GTIM_CCER_OFFSET, ccer);
  tim_putreg(priv, STM32_GTIM_EGR_OFFSET, egr);

  /* Set the ARR Preload Bit */

  tim_modifyreg(priv, STM32_GTIM_CR1_OFFSET, 0, GTIM_CR1_ARPE);

  /* Enable the timer counter */

  adc_timstart(priv, true);

  tim_dumpregs(priv, "After starting timers");

  return OK;
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: stm32h5_adc_initialize
 ****************************************************************************/

struct adc_dev_s *stm32h5_adc_initialize(int intf,
                                         const uint8_t *chanlist,
                                         int cchannels)
{
  struct adc_dev_s *dev;
  struct stm32_dev_s *priv;

  ainfo("intf: %d cchannels: %d\n", intf, cchannels);

  switch (intf)
    {
#ifdef CONFIG_STM32H5_ADC1
      case 1:
        ainfo("ADC1 selected\n");
        dev = &g_adcdev1;
        break;
#endif
#ifdef CONFIG_STM32H5_ADC2
      case 2:
        ainfo("ADC2 selected\n");
        dev = &g_adcdev2;
        break;
#endif
      default:
        aerr("ERROR: No ADC interface defined\n");
        return NULL;
    }

  /* Configure the selected ADC */

  priv = (struct stm32_dev_s *)dev->ad_priv;
  priv->cb = NULL;

  DEBUGASSERT(cchannels <= CONFIG_STM32H5_ADC_MAX_SAMPLES);
  if (cchannels > CONFIG_STM32H5_ADC_MAX_SAMPLES)
    {
      cchannels = CONFIG_STM32H5_ADC_MAX_SAMPLES;
    }

  priv->cchannels = cchannels;
  memcpy(priv->chanlist, chanlist, cchannels);

#ifdef CONFIG_PM
  if (pm_register(&priv->pm_callback) != OK)
    {
      aerr("ADC Power management registration failed\n");
      return NULL;
    }
#endif

  return dev;
}
#endif /* CONFIG_STM32H5_ADC1 || CONFIG_STM32H5_ADC2 */
#endif /* CONFIG_ADC */

